独習PHP:第10章 オブジェクト指向構文(クラス)
10.1 クラスの定義
PHPにはDateなどの標準クラスがあるが、もちろん自分で定義することができる。
ただしJavaやC#のようにオブジェクト指向言語というわけではないので、不可欠というわけではない
なので以下の二つをうまく混ぜ込んでいくと、PHPでは良い
PHPの柔軟性
オブジェクト指向言語のコード再利用性、保守性の向上
クラスの例
code:php
class Triangle {
public function __construct() { ... } // コンストラクタ
public function __destruct() { ... } // デストラクタ
// メンバ関数(メソッド)
public static function calculateArea(float $width, float $height): float { ... }
// メンバ変数(プロパティ)
private $width;
}
// public / private: アクセス修飾子 static: 修飾子
クラスの基本
1つのファイルに1つ定義して管理するのが良い
ファイル名でのクラス識別、オートローダーが使えることがメリット
役割を明確にする
Personという親クラスがStudent,BusinessPersonという子クラスを持つ、など分かりやすく
Pascal記法(単語の頭文字を大文字とする記法)で統一する
目的に応じてサフィックス(接尾辞)をつける
PersonException, PersonTestなど
機能が明確になるような単語を選定する
英単語をフルスペルで記述する
Tempなど、略語が広く知られているものであれば使ってもOK
プロパティ property(資産, 所有物)
名前と紐づけて覚えると良い。インスタンスに属する変数のこと。メンバ変数。
アクセス修飾子を付与できる
データ型を宣言できる(PHP7.4以降)
code:php
class Person {
public string $name;
}
$p = new Person();
$p->$name = '鈴木';
作成したインスタンスに、後からプロパティの追加もできる
$p->age = 30;
型に厳格なJavaなどだとこの実装は不可。PHP独自の設計と言える
メソッド
クラス内で定義された関数。メンバ関数。
先頭にアクセス修飾子を指定できる。省略した場合はpublicが適用される
$this
静的でないインスタンスメソッドの中でのみ利用できる特別な変数
$this->nameは、インスタンスの$nameプロパティを指す.
プロパティのように、後からメソッドを入れることもできるが割愛。
マジックメソッドでできる。P480参照
コンストラクタ
クラスの基本は、$p = new Personというように、new演算子でインスタンス化する
このタイミングで実行されるマジックメソッド
プロパティの初期化、クラス内部で利用する外部リソースの初期化の処理を書いたりする
code:php
class Person {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
$p = new Person('太郎'); // $this->nameに"太郎"が入る
PHP8.0から省略構文が使える。
デストラクタ
オブジェクトが破棄されるタイミングで実行されるマジックメソッド。
クラス内で使用したリソースを破棄する場合など、終了時の処理を記述する
以下のルールがあるので、アクセス修飾子は必ずpublicとする。
引数を受け取ってはいけない
戻り値を返してもいけない
code:php
public function __destruct() {
...
}
PHPのリソースのほとんどは終了タイミングで自動で破棄されるので、使うケースは稀
こんなマジックメソッドがあるよくらいで覚えておこう
静的メソッド
インスタンスを生成しなくてもクラスから呼べるメソッドのこと
普通のメソッドは$p = new Person(); $p->method()とする必要があるが、不要
ルール
メソッド定義に修飾子「static」を付与して定義する
呼び出すには::演算子を利用する
Person::method()
静的メソッドの中で$thisは使用できない
$thisは現在のインスタンスを表すキーワード
静的メソッドはインスタンスをnewで定義していないので、インスタンスが存在しないから呼べない。
理屈で覚えると結構わかりやすい
インスタンスメソッド(通常のメソッド))の呼び出しには::を使わない
静的プロパティ
クラスから呼ぶことができるプロパティのこと
同じように::を利用する
Person::$name
静的メソッドから静的プロパティを呼ぶときはselfキーワードを使う
self::$name
code:php
class Area {
public static $pi = 3.14;
public static function circle($rad) {
return pow($rad * 2) * self::$pi; // 静的メソッド内の静的プロパティ
}
}
print '円周率: ' . Area::$pi; // クラス外の静的プロパティ呼び出し
クラス定数
constキーワードで定義できるのは同じだが、アクセス修飾子を付与することができる
::演算子でアクセスができる。
クラスに関連する定数をまとめて管理できるので、読みやすい
code:php
class Area {
public const PI = 3.14;
...
}
print Area::PI // 3.14
10.2 カプセル化
クラス機能のうち、使い手に必要のないものを隠してしまうこと。ブラックボックス化。
例えばテレビ。使用者は電源をつけ、チャンネルを回すだけでよく、内部の回路の理解は必要ない
難解な箇所に手を触れて、プログラムに不具合が発生してしまうことを防ぐ
電源をつけたりチャンネルを回す機能がpublic
回路の操作、電源起動時の各挙動の確認などがprivateというイメージ
アクセス修飾子
public, private, protectedがある
protected: 現在のクラスとサブクラスでアクセスができることを表す
code:php
class Person {
private function show() {
print ('showメソッド');
}
}
$p = new Person;
$p->show();
// privateメソッドのため、エラー.
//インスタンスから呼び出すことはない用途で作られている関数であることが読み取れる
アクセサメソッド
code:php
class Person {
public $name; // プロパティ
}
$boy = new Person;
$boy->name = "太郎"; // アクセスできる
これはプログラム的には望ましくない
値を取得できることは良いとしても、何度も書き込み変更されることは設計的にあまりない
値の書き込みは安直にさせず、しっかりとメソッドとして済ませるようにする
getName()とかsetName()とかのメソッドのこと。
code:php
class Person {
private $name; // プロパティはprivate権限で定義し、外からの書き込みを防ぐ。
// $nameを取得するためのpublicメソッド
public function getName() {
return $this->name;
}
// $nameを新しく書き込むためのpublicメソッド
public function setName($new_name) {
$this->name = $new_name;
}
}
$boy = new Person();
$boy->setName('太郎');
print $boy->getName(); // 太郎
function setName(string $name): stringと型指定をすると誤った値の挿入を防げてより強固になる
継承 Inheritance
基になるクラスの機能を引き継ぎ、新機能を追加したり、元機能の一部を修正できる仕組み
継承元がスーパークラス、継承の結果できたクラスをサブクラスと呼ぶ
code:php
// Controllerという親クラスから、HomeControllerを作っている
class HomeController extends Controller
is_aの関係
SubClass is a SuperClassの関係.
class HomeController extends Controller
ホームコントローラは、コントローラの1種である、ということが成り立つ。
class Student extends Personなど、つながる意味を持った命名を意識する。
オーバーライド
スーパークラスのメソッドをサブクラスで上書きすること
親クラスのメソッドを、明示的に呼び出す
オーバーライドする際に、既存+αの機能とする時に便利
code:php
class BusinessPerson extends Person {
public function work(): void {
parent::work();
print 'ビジネスマンとして'; // +αの処理
}
}
// スーパークラスのコンストラクタを呼び出すこともできる
public function __construct($name) {
parent::__construct($name);
// ... サブクラスならではの、 +αの処理
}
オーバーライドを禁止する
final修飾子
親クラスで定義することで、サブクラスで使用することができなくなる
code:php
public final function work() { ... }
// クラスレベルで定義することで、HomeControllerからのサブクラスの作成を禁止することもできる
final class HomeController extends Controller { ... }
移譲
継承は親と子が密に結びついた関係で、拡張を前提とした機能
移譲は、別クラスの流用できそうなメリットをもらって、その機能を使うこと。
親と子の関係ではない
ポリモーフィズム多態性
同名のメソッドで、異なる挙動を実現させること
複数クラスで同じ名前のメソッドを定義して、呼び出すという挙動のことを言う
同じ目的(面積を求めると言う目的)のために、異なる名前を使うとややこしいのでこの仕組みを使う
code:php
class Figure {
// 親側で、仮のメソッドを定義しておく
public function getArea() {
return 0;
}
}
class Triangle extends Figure {
public function getArea() {
return $this->width * $this->height / 2;
}
}
class Square extends Figure {
public function getArea() {
return $this->width * $this->height;
}
}
$tri = new Triangle(10, 10);
$square = new Square(10, 10);
// 同じgetArea()だが、当然挙動は違う
$tri->getArea(); // 50
$square->getArea(); // 100
もっとポリモーフィズムを使う
上の例だと、getArea()をオーバーライドして使うんだなと言うことを考えられる
ただ実際はcalcArea()というように別のメソッドを実装して使ってしまうかも
なので、より明示的にそういったメソッドであることをabstractで示す.
code:php
class Figure {
// protected: クラス内部及びサブクラスから呼び出せる、
// abstract: 抽象的なメソッド getArea(). 返り値はfloat.
protected abstract function getArea(): float;
}
スーパークラス側ではなく、サブクラスで定義することを前提としたメソッドを抽象メソッドと呼ぶ。
インターフェイス
厳密には異なるが、内部のメソッドが全て抽象メソッドであるクラスのこと
code:php
interface IFigure {
function getArea(): float;
function getSpace(): String;
...
}
注意点
中身を持つメソッドやプロパティの定義はできない
配置できるのは抽象メソッド、定数だけ
abstractの指定は必要ない(抽象メソッドを入れる用途に使うのがインターフェイスなため)
アクセス修飾子の指定もできない。publicを使ってもいいが、意味なし
インターフェイスでわかるように、名称に気を遣ってもいい
IFugureというように、Interfaceであることを示しておく
メリット
多重継承ができる
必要な機能を必要だけ持った、サブクラスを実装することができる
code:php
// implementsを使用する
class Triangle implements IFigure {
// インターフェイスで実装されている抽象メソッドの中身を記入
public function getArea(): float {
return $this->width * $this->height / 2 ;
}
instanceof演算子
$変数 instanceof インターフェイス/クラス名で、インスタンスの状態についてT/Fで返してくれる
if ($fig instanceof IFigure) {
IFigureをimplementsできていることを確認とれたので、$figのメソッドを呼ぶ`という形で使える
無名クラス(匿名クラス)
名前を持たない、特定の文中で使用できるクラス
後で利用しないことがわかっている、その場限りのクラスを定義するために使う
code:php
new class { ..<< メソッドや定数などを書く >>.. }
// 継承もできる
$test = new class extends Area { ....
トレイト
再利用することができるコードを切り出しておくための仕組み。
断片的なクラスのようなもので、トレイトとして切り出したコードは後から個々のクラスに取り込める
code:php
trait MachineTrait {
private string $starting = 'Starting...Run!';
public function run(): void {
print $this->$starting;
}
}
class Fax {
use MachineTrait; // トレイトの使用宣言
public function send(): void {
print 'sending Fax .... sended!';
}
}
$fx = new Fax();
$fx->run(); // トレイトで宣言したものも使用できる
$fx->send();
トレイトと多重継承
トレイト機能は多重継承(implement)で賄えるのでは?
正しくは、インターフェイスとトレイトを両方使うのが綺麗なコード
インターフェイスとして、抽象メソッドで定義
トレイトとしてそのメソッドを用意する
そこから必要な要素を取り込んだクラスを作る
名前の衝突
code:php
trait TraitTest { public function hoge() { ... } }
class MyClass { public function hoge() { ... } }
class MyChild extends MyClass {
use TraitTest;
}
$c = new MyChild;
$c->hoge(); // TraitTestと、MyClassどっちのものが出力される?
優先順位がある
現在のクラスのhoge() > トレイトのhoge() > 親クラスのhoge()
トレイト同士で衝突することもあるので、対策ができる
code:php
use MyTrait1, MyTrait2 {
MyTrait1::hoge insteadOf MyTrait2; // MyTrait2のhogeの代わりに、1のhogeを使えという命令
// as演算子で別名を付与することもできる
MyTrait2::hoge as hoge_two
}
クラスオブジェクトの操作
クラスの代入は参照渡しが基本となっている
$x = $y;とかは値渡しが基本($xを変えても$yは変わらないという動作)
オブジェクトを値渡し(完全なコピーを作る)したいなら、cloneを使う
code:php
$p2 = clone $p1;
$p2->name = "二郎";
$p1->name // 太郎
$p2->name // 二郎
10.7 例外処理とオブジェクト指向プログラミング
発生しそうな問題をifで拾うのは良くない?
それでもOK
ただifで増やしすぎると、条件なのかチェック処理なのか分かりづらい
例外処理専用の構文を使うことで、可読性が上がる
例外クラスの種類
大元が、Exceptionクラス
LogicException
引数のミスなどはこっち。これはコードを直して対応すべき問題
RuntimeException
DB接続エラー、指定テーブルが存在しないなど、外部環境の問題のエラー
事前のコードチェックでは対策できないエラーはこっち
例外変数
一般的に$eで表すもの
例外処理の注意点
1. Exceptionを捕捉しない(やたらと使わない)
catchブロックの呼び出し
発生した例外がcatchで記述したクラスと一致/ 発生した例外の基底クラスだった時に呼ばれる
code:php
// 全ての例外を補足するとき
try {
...
} catch (Exception $e) { ... // Exceptionは全ての例外クラスの規定(親)なので、補足できる
Exceptionだと括りが大きすぎる
コード的には望ましくない場合も
より望ましい、下位の例外クラスがあった場合はそちらを定義したい
DBのエラーならPDOExceptionとしてcatchすべき、とか。
2. マルチキャッチ
code:php
// DBでエラー起こった時は〜、 エラー処理で止まった時は〜、...という構文
try {
...
} catch (PDOException $e) {
...
} catch (ErrorException $e) {
...
} catch (....
マルチキャッチ構文を使おう
code:php
try {
...
} catch (PDOException | ErrorException $e) {
...
}
3. catchブロックの記述順序
code:php
try {
...
} catch (Exception $e) {
...
} catch (PDOException $e) {
...
} catch (ErrorException $e) {
...
}
最初に大元のExceptionで括ってしまうと、それに全て反応してしまう
小さなものから順に書いていく
4. 例外をスローする、throw命令
例外はライブラリが発生させるばかりでなく、自分で発生させることもできる
code:php
class Website {
public static function getContents(string $url): string {
if (!isset($url)) {
throw new InvalidArgumentException('不正なURLです');
}
$data = @file_get_contents($url);
if (!$data) {
throw new RuntimeException('指定されたURLが見つかりません。');
}
return $data;
}
}
try {
} catch (RuntimeException | InvalidArgumentException $e) {
print "エラーメッセージ: {$e->getMessage()}";
}
//エラーメッセージ: 指定されたURLが見つかりません。
URLが不正な時はInvalid...クラスで例外処理を投げて
URLから情報を取得できなかったときはRuntime...クラスで例外処理を投げている
例外をthrowするときの注意点
Exceptionを投げない
識別できるように、なるべく下位に派生したクラスを投げよう
可能なら、標準例外を使う
OriginalExceptionというように、自分で独自の例外クラスを作ることができる
適切な例外クラスがあれば、それを使えば良い
例外を握り潰さない
空のcatchブロックを作らない
} catch (Exception $e) {}
privateメソッドではassertを使う
privateメソッドは、呼び出し元がアプリ内で特定されていることが前提
渡される値が信用できる状況だと言えるので、例外処理の手続きではなくassertを使うべき
code:php
function getTri($upper, $lower, $height): float {
assert($upper > 0 && $lower > 0 && $height > 0, 'assertion comment');
return ($upper + $lower) * $height / 2;
}
echo getTri(1,2,0) . "<br>";
// --------- assertが引数を判定してくれて、異なる場合はエラーを出してくれる ---------
// Fatal error: Uncaught AssertionError: assertion comment in /var/www/html/...
assert(条件式, 失敗時のエラーコメント)
与えられた条件式がfalseの時、エラー / 警告を投げる関数
php.iniのオプションでassertを無効化することができる
エラー報告の処理について
PHP5から例外処理が追加された
なのでそれ以前からある関数は、例外処理ではなくエラー報告での異常レポートとなる
エラー通知も例外処理で吐ける
code:php
try {
print 1 / 0;
// } catch (Exception $e) { // こっちだとキャッチできない
} catch (Error $e) {
echo $e->getMessage();
}
// 適当なエラーもErrorクラスでキャッチできる
try {
ageonvak;
} catch (Error $e) {
echo $e->getMessage();
}
エラー処理をset_error_handler()で例外処理とすることもできる
スタックトレース
例外が発生するまでに経てきたメソッド(関数)の数のこと
開始位置から呼び出し順に記録される
スタックトレースを確認すると、メソッド呼び出しの過程が誤っていないかどうか読み取れる
code:txt
Stack trace:
#3 {main} thrown in ... // これが最初に呼ばれた部分。ファイルと列が書いてある getTrace()関数など、スタックトレースを取得できる関数もある
アプリ独自の例外クラス
Exception親クラスを継承、接尾辞を指定するなど準備が必要。詳細はP553
10.8 マジックメソッド
PHPであらかじめ特定の役割を与えられたメソッドとして存在するもののこと
Laravelつながりのものだけ列挙する。__construct, __invokeなどが該当
クラスに対して特定の機能を実装できる
シグニチャ(名前と引数、戻り値の組み合わせ)と呼び出しのタイミングが固定
機能自体は空っぽなので、自分で実装する必要がある
__invokeメソッド
オブジェクトが関数形式で呼ばれた場合に実行される
code:php
class Animal {
public function __invoke(int $index) : mixed {
return $this->list$index; // private array $list(int $index)の値が参照される }
}
$list = new Animal(); // 関数形式. カッコで書かれている
print $list(1); // 'b'
10.9 名前空間namespace
クラスや関数の所属を表現すること。
code:php
namespace wings\selfphp\chap10;
class MyClass {
public static function showClass(): void {
print __CLASS__;
}
}
// wings\selfphp\chap10のMyClassクラスだということを表している
ルール
ファイルの先頭でないといけない
<?phpのすぐ後。改行などもダメ
1つのファイルにつける名前空間は、1つにしよう
名前解決について
code:php
namespace wings\selfphp\chap10; // 上で定義したMyClassと同じ名前空間
print MyClass::showClass(); // 同じ空間にいるので、クラスを定義していないファイルでも静的メソッドを呼び出せる
クラスのインポート
use命令を使って、インポート宣言をする
🌟トレイトは、クラスの中でuse命令を使って断片的なコードを呼ぶ行為なのでuseの使い方が少し違う
code:php
// 全然MyClassの名前空間は違うファイル
use wings\selfphp\chap10\MyClass; // MyClassクラスをインポート宣言
print MyClass::showClass(); // wings\selfphp\chap10\MyClassのクラスメソッドを使用できる
// まとめ書きすることもできる
use wings\selfphp\chap10\{MyClass, YourClass, HisClass} // 3つのクラスインポート
ルール
制御構文の中では使えない
関数やifで、この時はこれをインポート、という形では使えない
エイリアスの指定もできる
code:php
use wings\selfphp\chap10\sub\Myclqss as SubClass;
$sub = SubClass::function();
クラスの自動ローディング (オートローダー)
クラスは1ファイルに1つ記述することで、管理も見やすさもアップする
ただしこれだと、扱うクラスが増えた時問題がある
require_onceをいちいち書いたり、誤字や呼び忘れなどがある
オートローダーを使って解決する
存在しないクラスをインスタンス化する際に呼ばれ、対応ファイルをincludeするための仕組み
code:php
// オートローダーを登録するための関数
spl_autoload_register(function(string $name): void {
require_once "{$name}.php";
}
流れ
呼び出そうとしたクラスの名前が引数として渡される
オートローダーの中でrequire_once命令を呼び出す
これによって、クラスが呼ばれたタイミングで同名のクラスファイルを呼ぶ仕組みを取れる
code:php
<?php
require_once 'Autoloader.php';
$p = new Person('太郎'); // ここでオートローダー関数が呼ばれ、requireしてくれている
$p->show();
もっと楽に
Autoloader.phpは、どのファイルでも常に記述されることが前提になりそう
php.iniのauto_prepend_fileパラメータにファイルを書く
これで全てのコードを実行する際、先立ってそのファイルが呼ばれる仕組みになる
code:php.ini
auto_prepend_file = Autoloader.php
特定のディレクトリに対してだけ使いたい場合は?
chap10直下の時だけオートローダーを使いたいなど
.htaccessに記述しよう
ドキュメントルート配下のファイルに配置すると、該当フォルダとサブフォルダに対して適用されるパラメーターを設定することができる
code:.htaccess
<IfModule mod_php.c>
php_value auto_prepend_file "Autoloader.php"
</IfModule>
注意点
httpd.confの設定で、.htaccessの設定上書きを可能にしておく
php.iniでしか設定できないパラメーターもある
Composerを使った自動ローディング
上でオートロードを自作したが、composerがそういった仕組みを持ってくれてる
composer.jsonを指定の構文で編集しておく
composer dump-autoloadでオートローダーを自動生成する
🌟Laravelでコードが見つかりません!っていうエラーになった時、オートローダーをこれで再生成するのは、正しくクラスなどをもう一度読み込ませるため